﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="NHibernateProvider.cs" company="DissingNelleman">
//   
// All rights reserved. </copyright>
// <summary>
//   The  NHibernate provider.
//   Enables easy access to all NHibernate functions
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using log4net;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Dialect;
using NHibernate.Mapping.ByCode;
using NHibernate.Tool.hbm2ddl;
using Configuration = NHibernate.Cfg.Configuration;

namespace LiSA_Server.Base.NHibernate
{
    /// <summary>
    /// The n hibernate provider.
    /// </summary>
    public class NHibernateProvider
    {
        #region Fields

        /// <summary>
        /// The instance
        /// </summary>
        private static NHibernateProvider instance;

        /// <summary>
        /// The log.
        /// </summary>
        private readonly ILog log;

        /// <summary>
        /// The session factory.
        /// </summary>
        private ISessionFactory sessionFactory;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Gets the instance.
        /// </summary>
        public static NHibernateProvider Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new NHibernateProvider();
                }

                return instance;
            }
        }

        /// <summary>
        /// Prevents a default instance of the <see cref="NHibernateProvider"/> class from being created.
        /// </summary>
        private NHibernateProvider()
        {
            this.log = LogManager.GetLogger(typeof(NHibernateProvider));

            this.MapModelComponents();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the configuration.
        /// </summary>
        public Configuration Configuration { get; private set; }

        /// <summary>
        /// Gets a value indicating whether is db valid.
        /// </summary>
        public bool IsDbValid { get; private set; }

        /// <summary>
        /// Gets the session factory.
        /// </summary>
        public ISessionFactory SessionFactory
        {
            get
            {
                if (this.sessionFactory == null)
                {
                    this.sessionFactory = this.Configuration.BuildSessionFactory();
                }

                return this.sessionFactory;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The update db.
        /// </summary>
        public void UpdateDb()
        {
            var su = new SchemaUpdate(this.Configuration);
            su.Execute(false, true);

            if (su.Exceptions.Count > 0)
            {
                foreach (Exception ex in su.Exceptions)
                {
                    this.log.Error(string.Empty, ex);
                }
            }
        }

        /// <summary>
        /// The with session.
        /// </summary>
        /// <param name="action">
        /// The action.
        /// </param>
        public void WithSession(Action<ISession> action)
        {
            using (ISession session = this.SessionFactory.OpenSession())
            {
                action(session);
            }
        }

        /// <summary>
        /// The with session.
        /// </summary>
        /// <param name="action">
        /// The action.
        /// </param>
        /// <typeparam name="T">
        /// The return type of the action.
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        public T WithSession<T>(Func<ISession, T> action)
        {
            T result = default(T);

            this.WithSession(session => { result = action(session); });

            return result;
        }

        /// <summary>
        /// The with session and transaction.
        /// </summary>
        /// <param name="action">
        /// The action.
        /// </param>
        public void WithSessionAndTransaction(Action<ISession> action)
        {
            using (ISession session = this.SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                action(session);
                tx.Commit();
            }
        }

        /// <summary>
        /// The with session and transaction.
        /// </summary>
        /// <param name="action">
        /// The action.
        /// </param>
        /// <typeparam name="T">
        /// The return type of the action.
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        public T WithSessionAndTransaction<T>(Func<ISession, T> action)
        {
            T result = default(T);

            this.WithSessionAndTransaction(session => { result = action(session); });

            return result;
        }

        #endregion

        #region Methods

        /// <summary>
        /// The create configuration.
        /// </summary>
        private void CreateConfiguration()
        {
            switch (ConfigurationManager.AppSettings["DataBaseType"])
            {
                case "SQLiteConnection":
                    // SQLite configuration (Assumes sqliteDatabase.db file in \App_Data\sqliteDatabase.db)
                    this.Configuration = new Configuration();
                    this.Configuration.DataBaseIntegration(
                        c =>
                        {
                            c.Dialect<SQLiteDialect>();

                            if (ConfigurationManager.ConnectionStrings["SQLiteConnection"] == null
                                || ConfigurationManager.ConnectionStrings["SQLiteConnection"].ConnectionString == null)
                            {
                                throw new Exception();
                            }

                            c.ConnectionString = ConfigurationManager.ConnectionStrings["SQLiteConnection"].ConnectionString;
                            c.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
                            c.SchemaAction = SchemaAutoAction.Validate;
                        });
                    break;

                case "DefaultConnection":

                    this.Configuration = new Configuration();
                    this.Configuration.DataBaseIntegration(
                        c =>
                        {
                            c.Dialect<MsSql2008Dialect>();

                            if (ConfigurationManager.ConnectionStrings["DefaultConnection"] == null
                                || ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString == null)
                            {
                                throw new Exception();
                            }

                            c.ConnectionString =
                                ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
                            c.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
                            c.SchemaAction = SchemaAutoAction.Validate;
                        });
                    break;
            }
        }

        /// <summary>
        /// The map model components.
        /// </summary>
        private void MapModelComponents()
        {
            var mapper = new ModelComponentModelMapper();

            this.CreateConfiguration();
            var di = new DirectoryInfo(AssemblyDirectory);
            var fis = di.GetFiles("*.dll");
            var list = new List<Type>();

            var loadedAssemblies =
                AppDomain.CurrentDomain.GetAssemblies()
                    .Where(x => x.GetName().ToString().StartsWith("LiSA_Service."))
                    .ToList();
            var loadedAssemblyNames = new HashSet<string>();
            foreach (var loadedAssembly in loadedAssemblies)
            {
                loadedAssemblyNames.Add(loadedAssembly.GetName().Name + ".dll");
            }

            var assemblies = new List<Assembly>();
            foreach (var fileInfo in fis)
            {
                //if (!loadedAssemblyNames.Contains(fileInfo.ToString()))
                //{
                assemblies.Add(Assembly.LoadFile(fileInfo.FullName));
                //}
            }
            foreach (var asm in assemblies)
            {
                var mappings =
                    asm.GetTypes().Where(
                        t => t.GetInterfaces().Any(i => i == typeof(IConformistHoldersProvider)));
                foreach (
                Type t in mappings)
                {
                    try
                    {
                        mapper.AddMapping(t);
                    }
                    catch (MappingException e)
                    {
                        log.Error(e);
                    }
                }
            }

            // Compile mappings
            var hbmMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
            this.Configuration.AddMapping(hbmMapping);

            try
            {
                var sv = new SchemaValidator(this.Configuration);
                sv.Validate();
                this.IsDbValid = true;
            }
            catch (Exception ex)
            {
                // Not ok
                this.IsDbValid = false;

                // Log
                this.log.Error(ex.Message, ex);
            }
        }
        private static string AssemblyDirectory
        {
            get
            {
                var codeBase = Assembly.GetExecutingAssembly().CodeBase;
                var uri = new UriBuilder(codeBase);
                var path = Uri.UnescapeDataString(uri.Path);
                return Path.GetDirectoryName(path);
            }
        }

        private static string GetAssemblyPath(Assembly ass)
        {
            var codeBase = ass.CodeBase;
            var uri = new UriBuilder(codeBase);
            var path = Uri.UnescapeDataString(uri.Path);

            return path;
        }
        #endregion
    }
}